package com.rmfyzxfw.caseprocess.boot.converter;

import ch.qos.logback.classic.pattern.MessageConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import cn.hutool.core.util.StrUtil;
import org.apache.commons.lang3.StringUtils;

import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * logback日志打印脱敏
 * @author Gyy
 * @date 2020/11/08
 */
public class SensitiveDataConverter extends MessageConverter {

    /**
     * 日志输出长度限制
     */
    private static final int MSG_LENGTH_LIMIT = 5 * 1024;

    /**
     * 手机号正则匹配
     */
    private static final Pattern PHONE_REGEX = Pattern.compile("1[3|4|5|7|8][0-9]\\d{8}");
    /**
     * 身份证号正则匹配
     */
    private static final Pattern ID_CARD_REGEX = Pattern.compile("([1-9]\\d{5}(18|19|20)\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx])|(^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3})");

    /**
     * 是否为数字正则匹配<br/>
     * PS:<br/>
     *   身份证号和手机号在脱敏的过程中，出现非手机号和非身份证号也被脱敏的情况。<br/>
     *   因此在身份证号和手机号过滤方法中，增加所匹配的字符串的后一位是否为数字的判断，如果后一位为数字，则不脱敏<br/>
     *   如：138022139841，第12位为1，13802213984不脱敏
     */
    private static final Pattern DIGIT_REGEX = Pattern.compile("^[-\\+]?[\\d]+$");

    /**
     * 关键信息替换符
     */
    private static final String HIDE_PLACEHOLDER = "*";

    @Override
    public String convert(ILoggingEvent event){
        // 获取原始日志
        String requestLogMsg = event.getFormattedMessage();
        int length = Optional.ofNullable(requestLogMsg).map(String::length).orElse(0);
        if (length > MSG_LENGTH_LIMIT) {
            requestLogMsg = StrUtil.format("{}...", requestLogMsg.substring(0, MSG_LENGTH_LIMIT));
        }
        // 获取返回脱敏后的日志
        return filterSensitive(requestLogMsg);
    }

    /**
     * 对敏感信息脱敏
     * @param content 待脱敏内容
     * @return 脱敏后内容
     */
    public static String filterSensitive(String content) {
        try {
            if(StringUtils.isBlank(content)) {
                return content;
            }
            content = filterIdCard(content);
            return filterMobile(content);
        }catch(Exception e) {
            return content;
        }
    }

    /**
     * [身份证号] 展示前后各4位，其他隐藏 。<例子：1101**********5762>
     * @param content 待脱敏内容
     * @return 已对身份证号进行脱敏的内容
     */
    private static String filterIdCard(String content){
        return filterByRegex(content, ID_CARD_REGEX, 4, 4);
    }

    /**
     * [手机号码] 前三位，后四位，其他隐藏<例子:138******1234>
     * @param content 待脱敏内容
     * @return 已对手机号进行脱敏的内容
     */
    private static String filterMobile(String content){
        return filterByRegex(content, PHONE_REGEX, 3, 4);
    }

    /**
     * 通过正则表达式进行数据脱敏
     * @param content 待脱敏内容
     * @param pattern 正则
     * @param startShowLen 首部显示长度
     * @param endShowLen 尾部显示长度
     * @return 脱敏处理的内容
     */
    private static String filterByRegex(String content, Pattern pattern, int startShowLen, int endShowLen) {
        Matcher matcher = pattern.matcher(content);
        StringBuffer sb = new StringBuffer() ;
        int contentLen = content.length();
        while(matcher.find()){
            // 判断下一位是否为数字
            if (matcher.end() < contentLen) {
                String nextStr = content.substring(matcher.end(), matcher.end() + 1);
                if (DIGIT_REGEX.matcher(nextStr).matches()) {
                    continue;
                }
            }
            matcher.appendReplacement(sb, baseSensitive(matcher.group(), startShowLen, endShowLen)) ;
        }
        matcher.appendTail(sb) ;
        return sb.toString();
    }

    /**
     * 基础脱敏处理 指定起止展示长度 剩余用"KEY"中字符替换
     * @param str         待脱敏的字符串
     * @param startLength 开始展示长度
     * @param endLength   末尾展示长度
     * @return 脱敏后的字符串
     */
    private static String baseSensitive(String str, int startLength, int endLength) {
        if (StringUtils.isBlank(str)) {
            return "";
        }
        return StringUtils.left(str, startLength).concat(StringUtils.leftPad(StringUtils.right(str, endLength), str.length() - startLength, HIDE_PLACEHOLDER));
    }
}
